﻿using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using StockModel;
using Utils;

namespace TimeBarPlugin.Realtime
{
	internal class DataLoader : IDisposable
	{
		#region Attributes

		private readonly ObservableCollection2<StockGroup> _groups;
		private readonly string _stockName;

		private StockGroup _group;
		private StockGroupItem _stock;

		private readonly DataLoaderMessageProcessor _messageProcessor;

		#endregion

		#region Properties

		public ObservableCollection<Quotation> Data { get; private set; }

		#endregion

		#region Events

		public event EventHandler<StockFoundEventArgs> StockFound;

		#endregion

		#region Public Methods

		public DataLoader(ObservableCollection2<StockGroup> groups, string stockName)
		{
			_groups = groups;
			_stockName = stockName;

			this.Data = new ObservableCollection<Quotation>();

			_messageProcessor = new DataLoaderMessageProcessor(this.Data);
		}

		public void Start()
		{
			_messageProcessor.Start();

			this.SearchGroupAndStock();
		}

		public void Stop()
		{
			_messageProcessor.Stop();

			this.RemoveSearchHandlers();
			if (_stock != null)
			{
				_stock.Item.QuotationCalendar.GetLatestAvailableDay().Quotations.CollectionChanged -= this.Source_CollectionChanged;
			}
		}

		protected virtual void OnStockFound(string symbol)
		{
			if (this.StockFound != null)
			{
				this.StockFound(this, new StockFoundEventArgs(symbol));
			}
		}

		#endregion

		#region Event Handlers

		private void StockGroupItems_OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
		{
			if (e.Action == NotifyCollectionChangedAction.Add)
			{
				foreach (StockGroupItem stock in e.NewItems)
				{
					if (stock.Item.Id.Name == _stockName)
					{
						foreach (StockGroup stockGroup in _groups)
						{
							ObservableCollection2<StockGroupItem> groupItems = stockGroup.Items.GetReadLock();
							try
							{
								if (object.ReferenceEquals(groupItems, sender))
								{
									this.SetGroup(stockGroup);
									break;
								}
							}
							finally
							{
								stockGroup.Items.FreeReadLock();
							}
						}

						this.SetStock(stock);
						break;
					}
				}
			}
		}

		private void GroupItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
		{
			if (e.Action == NotifyCollectionChangedAction.Reset) // This means the group is being initialized (market open)
			{
				_messageProcessor.AddMessage(new Message(LoaderMessageType.ClearQuotations));
			}
			else if (e.Action == NotifyCollectionChangedAction.Add)
			{
				StockGroupItem stockGroupItem = e.NewItems.Cast<StockGroupItem>().FirstOrDefault(g => g.Item.Id.Name == _stockName);
				if (stockGroupItem != null)
				{
					this.SetStock(stockGroupItem);
				}
			}
		}

		private void Source_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
		{
			if (e.Action == NotifyCollectionChangedAction.Add)
			{
				Quotation currentQuotation = (Quotation)e.NewItems[e.NewItems.Count - 1]; // Only take the last quotation received
				_messageProcessor.AddMessage(new AddQuotationMessage(currentQuotation));
			}
		}

		#endregion

		#region Private Methods

		private void SearchGroupAndStock()
		{
			foreach (StockGroup stockGroup in _groups)
			{
				ObservableCollection2<StockGroupItem> stockGroupItems = stockGroup.Items.GetReadLock();

				try
				{
					StockGroupItem stock = stockGroupItems.FirstOrDefault(s => s.Item.Id.Name == _stockName);
					if (stock == null)
					{
						stockGroupItems.CollectionChanged += this.StockGroupItems_OnCollectionChanged; // Disposed
					}
					else
					{
						this.SetGroup(stockGroup);
						this.SetStock(stock);
					}
				}
				finally
				{
					stockGroup.Items.FreeReadLock();
				}
			}
		}

		private void RemoveSearchHandlers()
		{
			foreach (StockGroup stockGroup in _groups)
			{
				stockGroup.Items.CollectionChanged -= this.StockGroupItems_OnCollectionChanged;
			}
		}

		private void SetGroup(StockGroup group)
		{
			_group = group;

			this.RemoveSearchHandlers();

			_group.Items.CollectionChanged += this.GroupItems_CollectionChanged; // Disposed
		}

		private void SetStock(StockGroupItem stock)
		{
			if (_stock != null)
			{
				_stock.Item.QuotationCalendar.GetLatestAvailableDay().Quotations.CollectionChanged -= this.Source_CollectionChanged;
			}

			_stock = stock;
			_stock.Item.QuotationCalendar.GetLatestAvailableDay().Quotations.CollectionChanged += this.Source_CollectionChanged; // Disposed

			this.OnStockFound(stock.Item.Id.Code);
		}

		#endregion

		#region IDisposable

		protected bool Disposed { get; private set; }

		private void Dispose(bool disposing)
		{
			if (!this.Disposed)
			{
				this.InternalDispose(disposing);
			}

			this.Disposed = true;
		}

		protected virtual void InternalDispose(bool disposing)
		{
			if (disposing)
			{
				this.RemoveSearchHandlers();
				if (_stock != null)
				{
					_stock.Item.QuotationCalendar.GetLatestAvailableDay().Quotations.CollectionChanged -= this.Source_CollectionChanged;
				}
				_messageProcessor.Dispose();
			}

			// Dispose here only unmanaged objects 
			// Don’t use managed objects here because maybe 
			// they have been finalized already
		}

		public void Dispose()
		{
			this.Dispose(true);
			GC.SuppressFinalize(this);
		}

		~DataLoader()
		{
			this.Dispose(false);
		}

		#endregion
	}
}
